<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Style-Type" content="text/css" />
  <meta name="generator" content="pandoc" />
  <title>嵌入式 STM32、RTOS、Linux 开发</title>
  <style type="text/css">code{white-space: pre;}</style>
  <link rel="stylesheet" href="doc/linux-style.css" type="text/css" />
</head>
<body>
<div id="header">
<h1 class="title">嵌入式 STM32、RTOS、Linux 开发</h1>
</div>
<div id="TOC">
<ul>
<li><a href="#嵌入式-stm32rtoslinux-开发">嵌入式 STM32、RTOS、Linux 开发</a><ul>
<li><a href="#泵浦激光器同步控制">泵浦激光器同步控制</a></li>
<li><a href="#nm-泵浦激光器项目">940nm 泵浦激光器项目</a></li>
<li><a href="#智能车载终端项目">智能车载终端项目</a></li>
<li><a href="#基于线程池的并发服务器">基于线程池的并发服务器</a></li>
<li><a href="#基于空谱结合的卷积神经网络高光谱图像分类">基于空谱结合的卷积神经网络高光谱图像分类</a></li>
<li><a href="#美赛-m-奖项目介绍">美赛 M 奖项目介绍</a></li>
<li><a href="#我参与过的一些硬件电路设计">我参与过的一些硬件电路设计：</a></li>
</ul></li>
</ul>
</div>
<h1 id="嵌入式-stm32rtoslinux-开发">嵌入式 STM32、RTOS、Linux 开发</h1>
<h2 id="泵浦激光器同步控制">泵浦激光器同步控制</h2>
<p>这个项目是我在一家公司里面实现的，所以大部分代码不能开源出来，不够有些细节还是可以讨论。</p>
<h3 id="技术栈">技术栈</h3>
<ul>
<li><strong>硬件</strong> ：泵浦激光器、STM32 微控制器、串口通信模块、光电检测电路 OPT4001</li>
<li><strong>软件</strong> ：嵌入式 C 语言、串口通信协议、环形缓冲区数据结构</li>
<li><strong>通信协议</strong> ：自定义通信协议，包含帧头（4 字节）、数据长度、数据地址位、数据位、校验位，波特率 9600，16 进制收发</li>
</ul>
<h3 id="功能实现">功能实现</h3>
<ul>
<li><strong>功能制作</strong> ：实现了查询实时电流、查询当前最大电流、查询设置电流及设置电流等功能。依据泵浦的通信协议，通过 <code>SetTxMsg</code> 构造发送命令，利用 <code>SendMsg</code> 将命令打包成字节流经串口发送，再经 <code>ProcessUartResponse</code> 解析接收数据。接收数据采用环形缓冲区，串口中断时将数据写入缓冲区，另创建读取缓冲区处理函数读取数据。</li>
<li><strong>通信协议</strong> ：波特率 9600，16 进制收发，含帧头（4 字节）、数据长度、数据地址位、数据位和校验位。</li>
<li><strong>光电检测电路</strong> ：制作光电检测电路 OPT4001。提高灵敏度的方法：屏蔽外界光噪声，加滤波电路减参考电压噪声，采用前置放大器增信号分辨率，在软件上用滑动窗口平均滤波。放大器输入阻抗高、输出阻抗小。</li>
</ul>
<p>该通信协议的波特率为 9600，采用 16 进制接收和发送，包含以下部分： - 帧头 - 数据长度 - 数据地址位 - 数据位 - 校验位</p>
<p>此外，还制作了一些光电检测电路，例如 OPT4001。关于如何提高灵敏度： - 首先需要屏蔽掉外接的光噪声 - 加入滤波电路减少参考电压噪声 - 或者采用前置放大器放大信号增加分辨率 - 同时在软件上采用滑动窗口平均滤波</p>
<h2 id="nm-泵浦激光器项目">940nm 泵浦激光器项目</h2>
<p>这个项目从方案调研、、芯片选型和硬件的制作、还有系统的设计都是我自己查阅资料和文献得来的，所以有问题可以邮箱联系我，或者可以看我GitHub上的开源代码。</p>
<p><a href="https://github.com/dingpenglong/dingpenglong.github.io">项目地址</a></p>
<h3 id="smbus通信">SMBUS通信</h3>
<p>SMBUS通信并非一种常见的通信方式，因此需要查阅SMBUS通信的源码，这里我是看的ST公司的源码文档，里面详细记录了SMBUS通信。 SMBUS通信是什么？SMBus（System Management Bus）是由Intel于1995年推出的两线制串行通信总线，专为系统管理任务（如电源控制、温度监控等）设计，其基于I2C协议但针对可靠性和管理功能进行了优化。物理层采用开漏设计的SMBCLK（时钟线）和SMBDATA（数据线），需外接上拉电阻（通常至3.3V或5V），并遵循严格的电压阈值（如VIL=0.8V, VIH=2.1V）以增强抗噪能力，标准速率范围为10 kHz至100 kHz。协议层通过强制超时机制（如35ms无响应自动复位）、PEC校验（数据包错误检查）和预定义命令集（如0x44读取温度、0x14控制风扇）提升可靠性，同时支持扩展功能如SMBALERT#中断信号和主机通知协议，允许从设备主动上报事件。相较于I2C，SMBus在电气规范、超时限制及校验机制上更为严格，但速率通常较低（I2C可达5 MHz）。其广泛应用于笔记本电脑电池管理、服务器散热控制及嵌入式系统硬件监测等场景，开发时需注意上拉电阻匹配、协议合规性及工具调试，成为高可靠系统管理的核心通信方案</p>
<p><a href="https://www.stmcu.com.cn/Designresource/detail/application_note/706433">ST官方手册</a></p>
<h3 id="技术栈-1">技术栈</h3>
<ul>
<li><strong>硬件</strong> ：MCU 控制板、泵浦驱动板、ADS1220、TPS546D24A、DRB8412、二极管、电流防护保险丝、EMI 滤波器</li>
<li><strong>软件</strong> ：嵌入式 C 语言、FreeRTOS 操作系统、SPI 通信、SMBUS 通信、I2C 通信、PID 算法、MATLAB 系统函数辨别工具箱</li>
<li><strong>通信</strong> ：SPI、SMBUS、I2C 通信协议</li>
</ul>
<h3 id="一硬件设计">（一）硬件设计</h3>
<ul>
<li><strong>MCU 控制板</strong></li>
<li>有三个串口分别解析上位机命令、PID 参数设置和 PID 曲线、串口屏预留拓展</li>
<li>然后还有风扇控制接口：PWM 输出、PWM 捕获、GPIO 控制风扇关断引脚</li>
<li><p>其他接口用于 FPC 排线连接到功率板上</p></li>
<li><strong>功率板</strong></li>
<li>加了一个二极管防反接、电流防护保险丝、EMI 滤波</li>
<li>包含 ADS1220、TPS546D24A、drv8412 芯片，用于恒温、恒流驱动实现</li>
<li>采用四层板设计，可以更好地散热，同时将 PGND 和 AGND 分开</li>
<li><p>顶层和底层走线，第二层走 PGND，第三层走电源</p></li>
</ul>
<h3 id="二芯片通信">（二）芯片通信</h3>
<ul>
<li><strong>ADS1220</strong></li>
<li>采用 SPI 通信</li>
<li>写读取数据函数时需要等待 DRDY 信号才能采集数据</li>
<li><p>遇到的问题：通道转换过程中会发生数据错误</p></li>
<li><strong>TPS546D24A</strong></li>
<li>采用 SMBUS 通信</li>
<li>遇到的问题：SMBUS 资料较少，读取电流不准确，只能依靠 offset、gain 寄存器来设置读取电流的偏移量</li>
<li>TPS546D24A 的寄存器较多，配置了很多功能</li>
<li>从机地址不对，输出电压前需要设置 SCALE_LOOP 寄存器</li>
<li>每次进行 SMBUS 通信时，需要使用 SMBUS 检错函数，检查传进来的 pcontext 栈不出错，以及 statemachine 不为 0x00002</li>
<li><p>同时封装了很多检错 TPS546D24 的函数，读取相应寄存器即可获取数据</p></li>
<li><strong>DRV8412</strong></li>
<li>采用 I2C 通信</li>
<li><p>配置 PH/EN 模式和 PWM 模式</p></li>
</ul>
<h3 id="三pid-算法">（三）PID 算法</h3>
<p>增加了积分限幅、输出限幅等设置，并且给调节过程配置了缓上升调节，以达到安全性，可以设置步长、斜率速度。<br />
自整定 PID： - 利用 ZN 法，改变输入输出，获取 PID 波形的一个震荡周期 - 根据 ZN 公式计算 PID 参数</p>
<p>自适应 PID： - 当误差很大时，只采用 P 调节 - 当误差逐渐变小时，引入积分 - 当误差最小时，加入微分</p>
<p>使用 MATLAB 系统函数辨别工具箱，将输入输出数据传给 MATLAB，获取 PID 控制器的系统函数，然后进一步获取 P、I、D 参数。<br />
先找大致的 P、I、D 参数，然后再微调。<br />
比例项用于快速响应误差，积分项用于消除稳态误差，微分项用于抑制系统的振荡。 <strong>其实PID有很多高级的用法，但是我觉得最适合工程的PID才是最好的，模糊控制、神经网络等等都可以结合PID</strong></p>
<h3 id="四上位机设计">（四）上位机设计</h3>
<p>包含串口收发数据解析、PID 参数设定协议解析。</p>
<h4 id="串口收发数据">1.串口收发数据</h4>
<p>定义接收环形缓冲区，包含以下内容： - 数据指针 - 头指针 - 尾指针 - 当前缓冲区存储的数据量 - 队列最大的数据量</p>
<ul>
<li><strong>读操作</strong>：从头指针开始出队，直到队空，更新头指针。</li>
<li><strong>写操作</strong>：从尾部插入数据，直到队满则无法插入。</li>
</ul>
<h4 id="pid-串口解析协议">2.PID 串口解析协议</h4>
<p>数据帧格式： - 帧头：0x59485a43 - 命令码 - 数据 - 校验和</p>
<p>接收缓冲区同样使用环形缓冲区，包含以下内容： - 数据指针 - 头指针 - 尾指针 - 当前帧的长度 - 是否找到帧头的标志位</p>
<ul>
<li><strong>读并且解析</strong>：先找到帧头，计算校验和，再拷贝帧数据。</li>
<li><strong>写操作</strong>：直接从尾部插入数据，直到队满则无法插入。</li>
</ul>
<h3 id="五freertos部分">（五）Freertos部分</h3>
<p>对于这部分的软件架构设计呢，我主要是分成三部分的大内容，首先是恒流任务和恒温任务，他们的优先级都是一样的，然而串口的优先级是最高的。我们都知道Freertos是基于优先级调度任务的，同等优先级是基于时间片的，所以这里用二值信号量去判断是否串口中断是否来临，那么这样就可以有效的阻塞串口处理函数。串口触发时间呢，则采用事件组去触发任务，数据的传输则用消息队列，获取电流，获取温度，获取风扇的转速等等这些。</p>
<h4 id="任务模块划分">1. 任务模块划分</h4>
<ul>
<li><strong>恒流控制任务</strong><br />
</li>
<li>优先级：与恒温任务同级（非最高）<br />
</li>
<li>功能：实时调节电流输出<br />
</li>
<li><strong>恒温控制任务</strong><br />
</li>
<li>优先级：与恒流任务同级<br />
</li>
<li>功能：监控并维持系统温度<br />
</li>
<li><strong>串口通信任务</strong><br />
</li>
<li>优先级：最高级（抢占式调度）<br />
</li>
<li>功能：处理串口指令与数据交互</li>
</ul>
<h4 id="任务调度策略">2. 任务调度策略</h4>
<ul>
<li><strong>FreeRTOS优先级机制</strong><br />
</li>
<li>串口任务 &gt; 恒流/恒温任务（同优先级）<br />
</li>
<li>同优先级任务采用时间片轮转调度<br />
</li>
<li><strong>中断响应设计</strong><br />
</li>
<li>使用二值信号量阻塞串口中断服务函数<br />
</li>
<li>确保高优先级串口事件及时响应</li>
</ul>
<h4 id="同步与通信机制">3. 同步与通信机制</h4>
<ul>
<li><strong>事件驱动</strong><br />
</li>
<li>串口触发事件通过<strong>事件组</strong>通知关联任务<br />
</li>
<li><strong>数据传输</strong><br />
</li>
<li>采用<strong>消息队列</strong>传递：
<ul>
<li>电流数据<br />
</li>
<li>温度数据<br />
</li>
<li>风扇转速数据<br />
</li>
</ul></li>
<li><strong>资源保护</strong><br />
</li>
<li>关键操作通过信号量/互斥锁实现原子访问</li>
</ul>
<h3 id="六问题与解答">（六）问题与解答</h3>
<ol style="list-style-type: decimal">
<li><strong>为什么项目里面通信协议不用 1 个字节的帧头，而用 4 个字节的帧头？</strong></li>
</ol>
<ul>
<li>1 个字节的帧头优点：效率更高，资源利用率更高。</li>
<li>4 个字节的帧头的优点：防止误判，能够增加可靠性。</li>
</ul>
<ol start="2" style="list-style-type: decimal">
<li><strong>假如数据包的发送中的数据长度后面一段帧的内容丢失了，而下位机的内容是否需要一直检查帧长度不对，等待下一帧的发送，这样应该怎么解决？</strong></li>
</ol>
<ul>
<li>帧同步恢复：当发现长度字段与后续字段不匹配时，立即丢弃，搜索下一个帧头。</li>
<li>在串口中实现一个环形缓冲队列，然后只有每一帧发送过来之后才进行读取。</li>
</ul>
<ol start="3" style="list-style-type: decimal">
<li><strong>Makefile 的使用，如何开启/关闭 warning 和 error 提示？</strong></li>
</ol>
<ul>
<li>如果你想在编译时显示更多的警告信息，可以使用编译器的 <code>-Wall</code> 或 <code>-Wextra</code> 选项。这些选项会启用更多的警告提示。</li>
<li><code>CFLAGS</code>：C 编译器的选项</li>
<li><code>CC</code>：C 编译器</li>
<li><code>SRC</code>：源文件目录</li>
<li><code>OBJ</code>：目标文件目录</li>
<li><code>%</code>：匹配任意文件名</li>
<li><code>$&lt;</code>：表示依赖中的第一个文件</li>
<li><code>$@</code>：表示目标文件</li>
</ul>
<ol start="4" style="list-style-type: decimal">
<li><strong>C/C++ 的区别？讲一下具体？</strong></li>
</ol>
<ul>
<li>C++ 有封装、继承、多态。</li>
<li>多态：当一个派生类继承基类的时候，基类里面必须有一个或多个虚函数 <code>virtual</code>，通过基类的指针或引用来调用派生类的函数。派生类可以重写（<code>override</code>）这些虚函数，从而实现不同的行为。</li>
<li>多态的主要应用场景包括：
<ul>
<li>接口统一：通过基类的接口操作不同类型的对象。</li>
<li>代码复用：减少重复代码，提高代码的可维护性。</li>
</ul></li>
</ul>
<h2 id="智能车载终端项目">智能车载终端项目</h2>
<p>这个项目需要用到一个IMX6ULL开发板，正点原子和韦东山的都可以，然后买一个usb摄像头，就可以了。</p>
<p><a href="https://gitcode.com/open-source-toolkit/0f937/?utm_source=tools_gitcode&amp;index=top&amp;type=card&amp;webUrl&amp;isLogin=1">项目地址</a></p>
<h3 id="技术栈-2">技术栈</h3>
<ul>
<li><strong>软件</strong> ：QT 开发框架、QMediaPlayer 类、QVideoWidget 类、QNetworkAccessManager 类、QAudioRecorder 类、QAudioEncoderSettings 类</li>
<li><strong>硬件</strong> ：USB 摄像头、AP3216 光传感器、DHT11 温湿度传感器、GPS Eco 模块、WM8960 音频编解码芯片</li>
<li><strong>通信</strong> ：V4L2 API、I2C 通信、串口通信、HTTP 网络请求</li>
</ul>
<h3 id="功能设计">功能设计</h3>
<ul>
<li><strong>音乐播放器</strong> ：用 <code>QMediaPlayer</code> 类，通过 <code>stateChanged(QMediaPlayer::State state)</code> 判断状态实现播放、暂停、停止，需提前知晓路径。</li>
<li><strong>视频播放器</strong> ：同用 <code>QMediaPlayer</code> 类，但需 <code>setVideoOutput(QVideoWidget*)</code> 设置视频输出。</li>
<li><strong>倒车界面</strong> ：定时器（500ms）实时读取 AP3216 光传感器距离信息，传递到 QString 上显示。</li>
<li><p><strong>USB 相机</strong> ：经 <code>yuv_to_jpeg</code> 转格式，<code>V4l2_open()</code> 函数打开摄像头设备，配置 MJPG、分辨率，申请内核缓冲区并 mmap 映射到用户空间，<code>Vide_Show()</code> 定时器（30ms）读帧数据显示在 QLabel 上，按钮槽函数实现拍照、保存及跳转相册界面。</p></li>
<li><p><strong>天气界面</strong>：用 IP 地址获取天气信息，调用中华万年历 API（数据与中国天气网一致、JSON 格式、查询方便），<code>QNetworkAccessManager</code> 发送网络请求，解析 JSON 数据，定时器（1 小时）更新天气数据。</p></li>
<li><p><strong>语音识别界面</strong>申请语音识别账号，QT 开发录音功能，创建 <code>QAudioRecorder</code>，用 <code>QAudioEncoderSettings</code> 设音频参数，调用 <code>record()、pause()、stop()</code> 函数录音，录音数据上传服务端识别。</p></li>
</ul>
<h3 id="驱动设计">驱动设计</h3>
<ul>
<li><strong>音频驱动模块</strong> ：采用 WM8960 芯片，I2S 接口配置。修改内核使能驱动，移植 alsa-lib 和 alsa-utils 源码，实现录音、播放功能。</li>
<li><strong>AP3216 距离读取</strong> ：在 I2C1 节点加设备子节点，编写驱动，用 <code>i2c_transfer</code> 传输。</li>
<li><strong>DHT11 芯片驱动</strong> ：数据格式为 8 位温度整数 + 8 位小数 + 8 位湿度整数 + 8 位小数 + 8 位校验和，引脚读取可选查询、中断、内核自带驱动方式，流程包括启动、等待就绪、读数据、验证、拷贝。</li>
<li><strong>GPS 定位</strong> ：Eco 模块串口通信，支持 NAME 协议，解析 GPS 数据（如 RMC、GGA、VTG 等），用 <code>strsplit</code> 分割字符串写入结构体。</li>
<li><strong>USB 摄像头驱动</strong> ：用内核及 USB 驱动，修改配置及 LCD、QT 环境，需改进程 PID 号。</li>
</ul>
<h3 id="一qt-设计">（一）QT 设计</h3>
<h4 id="音乐播放器">1. 音乐播放器</h4>
<p>主要用到了 <code>QMediaPlayer</code> 这个类，通过 <code>stateChanged(QMediaPlayer::State state)</code> 判断状态，从而知道媒体何时暂停、播放或停止（前提是知道路径）。</p>
<h4 id="视频播放器">2. 视频播放器</h4>
<p>同样用到了 <code>QMediaPlayer</code> 类，但需要使用 <code>setVideoOutput(QVideoWidget*)</code> 设置视频输出。</p>
<h4 id="倒车界面">3. 倒车界面</h4>
<p>显示 AP3216 读取的光传感器距离，通过 500ms 的定时器实时读取距离信息，并传递到 <code>QString</code> 上显示。</p>
<h4 id="usb-相机">4. USB 相机</h4>
<ul>
<li>使用 <code>yuv_to_jpeg</code> 转换格式。</li>
<li><code>V4l2_open()</code> 函数：打开摄像头设备（如 <code>/dev/video2</code>），配置 MJPG 格式、分辨率，申请内核缓冲区并通过 <code>mmap</code> 映射到用户空间。</li>
<li><code>Vide_Show()</code>：30ms 触发的定时器槽函数，从摄像头缓冲区读取帧数据并显示在 <code>QLabel</code> 上。</li>
<li><code>On_pushButton_take_clicked()</code>：按钮槽函数，实现拍照并保存 JPG 文件到 <code>./photo/</code>。</li>
<li><code>On_pushButton_photos_clicked()</code>：关闭摄像头并跳转至相册界面。</li>
</ul>
<h4 id="实现步骤">实现步骤</h4>
<ol style="list-style-type: decimal">
<li>使用 V4L2 API 打开摄像头设备，配置格式（例如 YUV），并开始捕获帧。</li>
<li>将捕获的帧数据读取到内存中。</li>
<li>将数据转换为 Qt 可以处理的格式（例如 <code>QImage</code>），可能需要进行 YUV 转 RGB 的步骤。</li>
<li>将转换后的图像数据发送到 Qt 界面进行显示，例如通过信号槽机制更新 <code>QLabel</code> 或 <code>QGraphicsView</code> 的内容。</li>
</ol>
<h4 id="天气界面">5.天气界面</h4>
<p>利用 IP 地址实现对天气的获取： - 使用 IP 地址归属地查询当前位置，位置信息在查询天气时也会用到。 - 调用与天气相关的 API（例如中华万年历 API）获取本地天气信息。 - 推荐使用中华万年历 API 的原因： - 数据与中国天气网一致。 - 返回的数据是 JSON 格式，方便解析。 - 查询方便，支持城市名称或城市 ID 查询。 - 使用 <code>QNetworkAccessManager</code> 发送网络请求，解析 JSON 数据。 - 定时器（1 小时触发一次）用于定时更新天气数据。</p>
<h4 id="语音识别界面">6.语音识别界面</h4>
<ul>
<li>申请语言识别账号。</li>
<li>使用 QT 开发录音功能，创建 <code>QAudioRecorder</code>。</li>
<li>通过 <code>supportedAudioCodecs()</code> 获取可用的编码方式，使用 <code>QAudioEncoderSettings</code> 设置输入音频参数（编码格式、采样率、通道数、音频质量等）。</li>
<li>使用 <code>record()</code>、<code>pause()</code> 和 <code>stop()</code> 函数完成录音。</li>
<li>将录音数据上传到语音识别服务端进行识别。</li>
</ul>
<h3 id="二驱动设计">（二）驱动设计</h3>
<h4 id="音频驱动模块">1. 音频驱动模块</h4>
<ul>
<li>采用 WM8960 音频编解码芯片，使用 I2S 接口配置。</li>
<li>I2S 接口包含以下信号：</li>
<li>SCK：时钟线</li>
<li>WS：声道选择信号（1 表示左声道，0 表示右声道）</li>
<li>SD：两根数据线，用于放音和录音</li>
<li>IMX6ULL 使用 SAL 接口连接 WM8960。</li>
<li>将 WM8960 设备节点信息添加到 I2C1 节点下。</li>
<li>修改 SAI2 的 pinctrl 和时钟配置，修改 sound 节点。</li>
<li>NXP 官方已经提供了 WM8960 驱动，直接配置内核使能该驱动。</li>
<li>移植 alsa-lib 和 alsa-utils 源码，确保设备节点路径一致。</li>
<li>通过配置实现录音、播放音乐和播放视频的功能。</li>
</ul>
<h4 id="ap3216-距离读取">2. AP3216 距离读取</h4>
<ul>
<li>找到对应的 I2C1 节点，添加 ap3216c 的设备子节点，包含 compatible 和 reg 信息。</li>
<li>编写 ap3216 驱动，使用 <code>i2c_transfer</code> 函数进行数据传输。</li>
</ul>
<h4 id="dht11-芯片驱动">3. DHT11 芯片驱动</h4>
<ul>
<li>传送的数据格式：</li>
<li>8 位温度整数 + 8 位温度小数</li>
<li>8 位湿度整数 + 8 位湿度小数</li>
<li>+8 位校验和</li>
<li>引脚读取方式：</li>
<li>查询方式</li>
<li>中断方式</li>
<li>内核自带的 DHT11 驱动程序</li>
<li>流程：</li>
<li>发送高脉冲启动 DHT11</li>
<li>等待 DHT11 就绪</li>
<li>读取 5 字节数据</li>
<li>根据校验码验证数据</li>
<li>使用 <code>copy_to_user</code> 将数据传递给用户空间</li>
</ul>
<h4 id="gps-定位">4.GPS 定位</h4>
<ul>
<li>使用 Eco 模块通过串口通信，支持标准的 NMEA 协议。</li>
<li>解析 GPS 数据（例如 RMC、GGA、VTG 等）：</li>
<li>RMC 结构体数据包括：UCT 时间、状态、纬度、精度、速度、方位角、UTC 日期、磁偏角、磁偏角方向、模式。</li>
<li>使用 <code>strsplit(&amp;s, &quot;,&quot;)</code> 分割字符串，将数据写入结构体中。</li>
</ul>
<h4 id="usb-摄像头驱动">5.USB 摄像头驱动</h4>
<ul>
<li>使用内核提供的 USB 驱动。</li>
<li>修改 USB 驱动、LCD 驱动、QT 环境。</li>
<li>修改 USB 驱动的进程 PID 号。</li>
</ul>
<h4 id="lcd-驱动">6.LCD 驱动</h4>
<p>在 LCDinfo 节点下，修改为适配自己分辨率型号的配置。</p>
<h3 id="三提问问题">（三）提问问题</h3>
<h4 id="音乐播放器-1">1.音乐播放器</h4>
<p><strong>问题</strong>：你如何实现音乐播放器的播放、暂停和停止功能？<br />
<strong>答案</strong>：音乐播放器主要使用了 <code>QMediaPlayer</code> 类。通过监听 <code>stateChanged</code> 信号，可以判断播放器的状态（播放、暂停、停止）。当状态变化时，根据 <code>state</code> 的值执行相应的操作，例如播放时调用 <code>play()</code>，暂停时调用 <code>pause()</code>，停止时调用 <code>stop()</code>。播放器需要提前知道媒体文件的路径。</p>
<h4 id="视频播放器-1">2.视频播放器</h4>
<p><strong>问题</strong>：视频播放器和音乐播放器的区别是什么？<br />
<strong>答案</strong>：视频播放器同样使用 <code>QMediaPlayer</code> 类，但需要通过 <code>setVideoOutput(QVideoWidget*)</code> 设置视频输出的显示界面。音乐播放器只需要处理音频流，而视频播放器需要同时处理音频和视频流。</p>
<h4 id="倒车界面-1">3.倒车界面</h4>
<p><strong>问题</strong>：倒车界面如何实时更新光传感器的距离信息？<br />
<strong>答案</strong>：倒车界面通过定时器（500ms 触发一次）实时读取 AP3216 光传感器的距离信息。读取到的数据通过 <code>QString</code> 传递到界面上显示。</p>
<h4 id="usb-相机-1">4.USB 相机</h4>
<p><strong>问题</strong>：如何实现 USB 相机的视频显示？<br />
<strong>答案</strong>：使用 <code>V4L2_open()</code> 函数打开摄像头设备（如 <code>/dev/video2</code>），配置为 MJPG 格式，并设置分辨率。通过 <code>mmap</code> 将内核缓冲区映射到用户空间。定时器（30ms 触发一次）从缓冲区读取帧数据，并显示在 <code>QLabel</code> 上。</p>
<h4 id="天气信息">5.天气信息</h4>
<p><strong>问题</strong>：如何通过 IP 地址获取天气信息？<br />
<strong>答案</strong>：使用 <code>QNetworkAccessManager</code> 发送网络请求，调用中华万年历 API 获取天气信息。API 返回的数据是 JSON 格式，通过解析 JSON 数据获取天气信息。定时器（1 小时触发一次）用于定时更新天气数据。</p>
<p><strong>问题</strong>：为什么选择中华万年历 API？<br />
<strong>答案</strong>：中华万年历 API 的数据与中国天气网一致，返回的数据是 JSON 格式，方便解析，且查询方便（支持城市名称或城市 ID）。</p>
<h4 id="语音识别">6.语音识别</h4>
<p><strong>问题</strong>：如何实现语音识别功能？<br />
<strong>答案</strong>：使用 <code>QAudioRecorder</code> 类实现录音功能。通过 <code>QAudioEncoderSettings</code> 设置音频参数（编码格式、采样率、通道数等）。调用 <code>record()</code>、<code>pause()</code> 和 <code>stop()</code> 函数完成录音。录音数据上传到语音识别服务端进行识别。</p>
<h4 id="音频驱动">7.音频驱动</h4>
<p><strong>问题</strong>：如何配置 WM8960 音频驱动？<br />
<strong>答案</strong>：使用 I2S 接口配置 WM8960 音频编解码芯片。修改内核配置，使能 WM8960 驱动。移植 alsa-lib 和 alsa-utils 源码，确保设备节点路径一致。通过配置实现录音、播放音乐和播放视频的功能。</p>
<h4 id="距离读取">8. 距离读取</h4>
<p><strong>问题</strong>：如何实现 AP3216 距离读取？<br />
<strong>答案</strong>：在 I2C1 节点下添加 AP3216 设备子节点，包含 compatible 和 reg 信息。编写 AP3216 驱动，通过 <code>i2c_transfer</code> 函数读取传感器数据。 <strong>DHT11 温湿度传感器的单总线协议编写</strong>： - 在进行数据读取过程中需要 <code>local_irq_disable</code> 屏蔽中断标志位，从而禁止内核的抢占。</p>
<h4 id="gps-数据解析">9. GPS 数据解析</h4>
<p><strong>问题</strong>：如何解析 GPS 数据？<br />
<strong>答案</strong>：使用 Eco 模块通过串口通信，解析 GPS 数据（例如 RMC、GGA、VTG 等）。通过 <code>strsplit</code> 函数分割字符串，将数据写入结构体中。</p>
<h4 id="摄像头帧格式">10.摄像头帧格式</h4>
<p><strong>问题</strong>：USB 摄像头驱动的定时器为什么要每隔 30ms 读取一次数据？<br />
<strong>答案</strong>：30ms 定时器对应每秒 30 帧，这是常见的流畅视频显示的帧率。 <strong>USB 摄像头帧格式转换</strong>： - 摄像头读取的帧格式需要从 YUV 转换为 JPEG 格式</p>
<p><strong>问题</strong>：对于摄像头读取的格式有没有做变换？<br />
<strong>答案</strong>：做了变换。通常 USB 摄像头读取的是 YUV 格式的视频数据，需要编写一个 YUV 转 JPEG 格式的函数进行读取。关键函数是 libjpeg 库中的 <code>jpeg_compress</code> 函数。</p>
<h2 id="基于线程池的并发服务器">基于线程池的并发服务器</h2>
<p>开源项目，b站随便找一个就可以，这个主要是让我熟悉操作系统的一些条件变量、自旋锁和socket套接字概念的。</p>
<h3 id="技术栈-3">技术栈</h3>
<ul>
<li><strong>软件</strong> ：C/C++ 语言、线程池算法、Socket 编程</li>
<li><strong>通信</strong> ：TCP/IP 协议、Socket 通信</li>
</ul>
<h3 id="一线程池的原理">（一）线程池的原理</h3>
<p>线程池分为三部分： 1. <strong>任务队列</strong>：用于存储待处理的任务，工作线程从队列中获取任务进行处理。 - 通过线程池的 API 接口，将待处理的任务添加到任务队列中。 - 已处理的任务会被从任务队列中删除。 - 线程池的使用者（调用线程池函数往队列添加任务的线程）是生产者线程。</p>
<ol start="2" style="list-style-type: decimal">
<li><strong>工作线程</strong>：任务队列的消费者，负责从队列中读取任务并执行。</li>
</ol>
<ul>
<li>工作线程不断从任务队列中读取任务并处理。</li>
</ul>
<ol start="3" style="list-style-type: decimal">
<li><strong>管理者线程</strong>：只能有一个，负责管理线程池的运行状态。</li>
</ol>
<ul>
<li>周期性地检测任务队列的任务数量以及忙状态的工作线程个数。</li>
<li>当任务过多时，可以适当创建一些新的工作线程。</li>
<li>当任务过少时，可以适当销毁一些工作线程。</li>
</ul>
<h3 id="二socket-通信">（二）Socket 通信</h3>
<p>服务器端的通信流程： 1. 创建 socket 描述符。 2. 使用 <code>bind</code> 绑定 IP 和端口。 3. 调用 <code>listen</code> 进入监听状态。 4. 调用 <code>accept</code> 接受客户端的连接请求。 5. 通过 <code>connect</code> 与客户端建立连接，完成通信。</p>
<h3 id="三多线程">（三）多线程</h3>
<p>服务端主线程在这里的主要任务是：按照Socket通信流程往下走，并发操作在哪里呢？ 第四步的<code>accept</code>接受到客户端的连接请求后，创建子线程<code>pthread_create</code>让通信的处理流程则是在子线程中运行，然后调用pthread_detach去回收子线程资源 注意是用<code>detach</code>而不是用<code>join</code>函数 阻塞。然后再close(fd)关闭文件描述符。通信的流程都在子线程的<code>working</code>里面。 那么需要给子线程传进去一些参数：信息结构体<code>sockinfo{sockadd_in addr,int fd}</code> 需要有socket的地址和 文件描述符fd。</p>
<h3 id="四线程同步">（四）线程同步</h3>
<p>线程同步体现在那里？ 多个线程对同一个资源需要同时读写操作，就需要同步操作了。 之前提到声明了一个全局的结构体数组<code>sockinfo infos[512]</code> 在主线程中要确定数组fd的数量 而这里是不需要线程同步的。 在线程池中是需要进行线程同步的。</p>
<h3 id="五套接字使用线程池的思路">（五）套接字使用线程池的思路：</h3>
<ul>
<li>和客户端建立连接和通信都是在工作线程中，也都是在子线程中去进行的。</li>
<li>而主线程只需要构建socket知道accpet，将子线程放到线程池里面。</li>
<li>所以主线程每次accpet后，添加通信的任务到线程池里面。</li>
</ul>
<h3 id="六线程池实现思路">（六）线程池实现思路</h3>
<ul>
<li>定义任务队列和线程池结构体，线程池结构体包含任务队列、容量、当前任务个数、队头和队尾，管理者和工作者的线程ID，还有一些最大最小线程池数量、当前存活的线程、忙的线程</li>
<li>创建线程池：根据传进的参数去确定线程池结构体实例的参数</li>
<li>销毁线程池：根据线程池结构体的实例去释放内存</li>
<li>给线程池添加任务：就是给线程池的任务结构体去传递回调函数和一些参数。这里需要给线程池上锁</li>
<li>工作线程：给线程池上锁，条件变量阻塞工作线程，从队列里面拿出一个任务，任务队列头结点向后移动</li>
<li>管理者：每次相隔3s检测一次，当任务的个数&gt;存活的线程个数 &amp;&amp; 存活的线程数<最大线程数，则添加工作线程，若忙的线程*2 < 存活的线程数 && 存活的线程>最小线程数，则销毁工作线程。</li>
</ul>
<h2 id="基于空谱结合的卷积神经网络高光谱图像分类">基于空谱结合的卷积神经网络高光谱图像分类</h2>
<p>这个项目是我结合实验室的一个成像模型的预训练，就是需要通过实验室特有的成像模型获取数据，然后对数据进行处理分类。大家有时间可以去看看关联成像，这部分也是我实验室的一个研究方向，但是这个跟嵌入式没啥关系，当作一个拓展吧。</p>
<h3 id="技术栈-4">技术栈</h3>
<ul>
<li><strong>软件</strong> ：Python 语言、TensorFlow 或 PyTorch 深度学习框架</li>
<li><strong>算法</strong> ：卷积神经网络（CNN）、支持向量机(SVM)、随机森林、光谱降维、空间特征提取、全局特征融合、分类与优化算法</li>
</ul>
<h3 id="算法步骤">算法步骤</h3>
<p>输入数据为尺寸 526×345×111 的光谱图像，设计轻量化卷积神经网络：</p>
<ol style="list-style-type: decimal">
<li><strong>光谱降维</strong> ：用 1×1 卷积将 111 通道压缩至 32 通道，去冗余降复杂度。</li>
<li><strong>空间特征提取</strong> ：堆叠多个 3×3 卷积块，后接最大池化层，网络加深时缩小空间尺寸（345×526→10×16），通道数翻倍（32→64→128→256→512），平衡信息损失与语义表达。</li>
<li><strong>全局特征融合</strong> ：以全局平均池化（GAP）替代全连接层，压缩特征图至 512 维向量，防参数爆炸。</li>
<li><strong>分类与优化</strong> ：经 Dropout 和 Softmax 输出概率，用批量归一化（BatchNorm）加速训练防过拟合。</li>
</ol>
<h2 id="美赛-m-奖项目介绍">美赛 M 奖项目介绍</h2>
<p>这个比赛离我很久远了，很多内容都想不起来了，但是还是去翻翻往年题目去作了一些总结，为什么我要写这个呢，因为某菊厂给我免笔试就是因为这个，哈哈哈是不是很值得？菊厂的笔试还是很难的，所以大家有时间要多比赛。</p>
<h3 id="技术栈-5">技术栈</h3>
<ul>
<li><strong>软件</strong> ：Maltab语言、数据挖掘与机器学习算法库</li>
<li><strong>算法</strong> ：K-means 聚类算法、模拟退火算法、轮廓系数评估指标</li>
</ul>
<h3 id="项目内容">项目内容</h3>
<p>以经纬度为输入，用 K-means 聚类算法划成 8 区域，参考火势强度作权重，轮廓系数评指标，输出中心坐标、火点数量、总火势强度。设计单位区域无人机需求数公式，指标含火势总量、无人机灭火能力、紧急系数、火势扩散速度。用模拟退火算法求全局最优分配，随机分配无人机，满足总数量约束，目标函数为灭火时间 + 权重因子 × 未覆盖惩罚，引入交叉验证评聚类模型。</p>
<h2 id="我参与过的一些硬件电路设计">我参与过的一些硬件电路设计：</h2>
<p>虽然画得一般，但也是自己设计的，也花了一点心思：</p>
<p><a href="https://dingpenglong.com/picture/pcb_desgin/pump_contrl.jpeg">点击查看pump_contrl电路PCB</a></p>
<p><a href="https://dingpenglong.com/picture/pcb_desgin/pump_driver.jpeg">点击查看pump_driver电路PCB</a></p>
<p><a href="https://dingpenglong.com/picture/pcb_desgin/tec_driver.jpeg">点击查看其他硬件1</a></p>
<p><a href="https://dingpenglong.com/picture/pcb_desgin/pid_curr_temp.jpeg">点击查看其他硬件2</a></p>
</body>
</html>
